package org.openmore.opc.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.RealmSecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.openmore.common.CommonUtils;
import org.openmore.common.data.ImgCaptcha;
import org.openmore.opc.framework.ExceptionPrint;
import org.openmore.common.exception.OpenmoreException;
import org.openmore.common.redis.RedisOps;
import org.openmore.opc.dto.LoginParams;
import org.openmore.opc.dto.UserProfileDto;
import org.openmore.opc.entity.Permission;
import org.openmore.opc.entity.Role;
import org.openmore.opc.entity.User;
import org.openmore.opc.entity.enums.LoginType;
import org.openmore.opc.entity.enums.UserType;
import org.openmore.opc.framework.MyThreadLocalConfig;
import org.openmore.opc.service.AccountService;
import org.openmore.opc.service.PermissionService;
import org.openmore.opc.service.RoleService;
import org.openmore.opc.service.UserService;
import org.openmore.opc.shiro.IRealmClearCache;
import org.openmore.opc.shiro.OpenmoreToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Service
public class AccountServiceImpl extends BaseServiceImpl implements AccountService {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private RoleService roleService;

    @Autowired
    private UserService userService;

    @Autowired
    private PermissionService permissionService;

    @Autowired
    private RedisOps redisOps;

    @Autowired
    private ExceptionPrint exceptionPrint;


    /**
     * 获得当前登录的用户
     *
     * @return
     */
    private User getLoginUser() {
        // 单一线程里只获得一次，不可跨线程（不同请求里使用）
        User user = MyThreadLocalConfig.getUser();
        if (user != null) {
            return user;
        }
        String userId = (String) SecurityUtils.getSubject().getPrincipal();
        System.out.println("==>o:" + userId);
        if (StringUtils.isEmpty(userId)) {
            return null;
        }
        Session session = SecurityUtils.getSubject().getSession();
        Collection<Object> keys = session.getAttributeKeys();
        String userString = (String) SecurityUtils.getSubject().getSession().getAttribute(userId);
        if (StringUtils.isEmpty(userString)) {
            this.logout();
            return null;
        }
//        System.out.println("==>userString:" + userString);
        try {
            user = JSONObject.parseObject(userString, User.class);
            MyThreadLocalConfig.setUser(user);
        } catch (Exception e) {
            return null;
        }
        return user;
    }

    /**
     * 执行shiro登录
     *
     * @param token
     */
    public void shiroLogin(OpenmoreToken token) {
        Subject subject = SecurityUtils.getSubject();
//      已经授权过的，重新登录，先退出
        if (subject.isAuthenticated()) {
            this.logout();
        }
//      未登录，执行登录
        if (!subject.isAuthenticated()) {
            subject.login(token);
            if (subject.isAuthenticated()) {
                User user = null;
//              shiro登录成功，没有找到员工，则尝试找用户表，分三种登录情况
                if (token.getType() == LoginType.PASSWORD || token.getType() == LoginType.CAPTCHA) {//密码登录
                    user = userService.getUserByPhone(token.getTenantId(), CommonUtils.trimPhonePrefixCode(token.getUsername()));
                } else if (token.getType() == LoginType.WECHAT_OPEN || token.getType() == LoginType.QQ
                        || token.getType() == LoginType.WEIBO|| token.getType() == LoginType.WECHAT_MINIAPP) {//微信登录
                    user = userService.getByOpenid(token.getType(), token.getUsername());
                } else {
                    throw new OpenmoreException(4001, "非法登录！");
                }
                MyThreadLocalConfig.setUser(user);
                //在此处将用户信息存入缓存
                SecurityUtils.getSubject().getSession().setAttribute(user.getId(), JSON.toJSONString(user));
            }
        }

    }

    @Override
    public UserProfileDto login(HttpServletRequest request, String tenantId, LoginParams params) {
        if(params.getType() == null) {
            throw new OpenmoreException("登录类型不能为空");
        }
        // 有图片验证码，先校验
        if(params.getImgCaptcha() != null && StringUtils.isEmpty(params.getImgCaptcha())) {
            ImgCaptcha.verifyImgCaptcha(request, params.getImgCaptcha());
        }
        if(params.getType() == LoginType.PASSWORD) {
            if(StringUtils.isEmpty(params.getPassword()) || StringUtils.isEmpty(params.getPhone())) {
                throw new OpenmoreException("登录手机号和密码不能为空");
            }
            // 执行登录操作
            String phoneNum;
            if (!StringUtils.isEmpty(params.getCode())) {
                phoneNum = params.getCode() + "_" + params.getPhone();
            } else {
                phoneNum = params.getPhone();
            }
            OpenmoreToken token = new OpenmoreToken(tenantId,params.getType(), phoneNum,
                    params.getPassword(), params.getLocationList());
            this.shiroLogin(token);
            insertLog("用户登录：" + token.getUsername());
        } else if(params.getType() == LoginType.CAPTCHA){//手机号验证码登录
            if(StringUtils.isEmpty(params.getCaptcha()) || StringUtils.isEmpty(params.getPhone())) {
                throw new OpenmoreException("登录手机号和验证码不能为空");
            }
            verifyCaptcha(tenantId, params.getPhone(), params.getCaptcha());
            String phoneNum;
            if (!StringUtils.isEmpty(params.getCode())) {
                phoneNum = params.getCode() + "_" + params.getPhone();
            } else {
                phoneNum = params.getPhone();
            }
            OpenmoreToken token = new OpenmoreToken(tenantId, params.getType(), phoneNum,
                    params.getPassword(), params.getLocationList());
            this.shiroLogin(token);
        }else if(params.getType() == LoginType.QQ || params.getType() == LoginType.WEIBO
                || params.getType() == LoginType.WECHAT_OPEN || params.getType() == LoginType.WECHAT_MINIAPP){
            if(StringUtils.isEmpty(params.getOpenid())) {
                throw new OpenmoreException("三方登录信息不能为空");
            }
            // 执行登录操作
            OpenmoreToken token = new OpenmoreToken(tenantId, params.getType(), params.getOpenid(),
                    "", params.getLocationList());
            this.shiroLogin(token);
            insertLog("用户三方登录：" + token.getUsername());
        }
//        if (params.getLocationList() == null && params.getLocationList().size() == 0) {
//            throw new OpenmoreException("没有位置信息，无法登录");
//        }

        return getLoginInfo();
    }

    /**shiro退出登录*/
    @Override
    public void logout() {
//      执行登录操作
        insertLog("用户退出登录");
        Subject subject = SecurityUtils.getSubject();
        RealmSecurityManager securityManager = (RealmSecurityManager) SecurityUtils.getSecurityManager();
        IRealmClearCache realm = (IRealmClearCache) securityManager.getRealms().iterator().next();
        realm.clearCache();
        subject.logout();

    }

    /**
     * 获得图片验证码图片流
     * @throws OpenmoreException
     */
    public void getImgCaptcha(HttpServletRequest request, HttpServletResponse response, int width, int height) throws OpenmoreException {
        String randCode = ImgCaptcha.getRandCode(request, response, width, height);
        logger.debug(">> rand Code : " + randCode);
    }

    /**
     * 校验图片验证码
     * @param request
     * @throws OpenmoreException
     */
    public void verifyImgCaptcha(HttpServletRequest request, String captcha) throws OpenmoreException {
        ImgCaptcha.verifyImgCaptcha(request, captcha);
    }

    /**
     * 发送短信验证码
     */
    @Override
    public String sendCaptcha(String tenantId, String mobile) throws OpenmoreException {
        logger.debug(">> sendCaptcha(" + tenantId + mobile + ")");
        /*long ttl = redisOps.ttl(mobile);
        if (ttl > 60) {
            throw new OpenmoreException("1分钟内只能发送一次！");
        }*/
        String code = CommonUtils.randomNumber(4);
        String jsonParams = "{\"code\":\"" + code + "\"}";
        //TODO :发送短信
        //String result = smsService.sendSms(jsonParams, SmsServiceImpl.SMS_SEND_CODE, mobile);
        String result = "";
        try {
            //保存到cache里
            redisOps.set(tenantId + mobile, code, 300);//5分钟有效
            return result;
        } catch (OpenmoreException e){
            return e.getMsg();
        }catch (Exception e) {
            throw new OpenmoreException("发送缓存出错");
        }
    }


    /**
     * 向登录邮箱用户的手机号发送短信验证码
     */
    @Override
    public String sendCaptchaByLoginEmail(String tenantId, String email) throws OpenmoreException {
        logger.debug(">> sendCaptchaByLoginEmail(" + email + ")");
        User user = userService.getUserByEmail(tenantId, email);
        if (user == null) {
            throw new OpenmoreException("找不到登录用户的信息");
        }
        String code = CommonUtils.randomNumber(4);
        String text = "您的验证码是" + code;
        //TODO :发送短信
        //smsService.sendSms(null, text, staff.getPhone());
        try {
            //保存到cache里
            redisOps.set(tenantId + user.getPhone(), code, 30 * 60);
        } catch (Exception e) {
            exceptionPrint.print(this.getClass(), e);
            throw new OpenmoreException("发送缓存出错");
        }
        return "短信码发送成功";
    }

    /**
     * 已注册用户直接登录，新用户没有手机号，需要传递手机号和验证码参数绑定手机号码
     * */
    @Transactional
    @Override
    public UserProfileDto phoneBind(String tenantId, LoginType type, String openid, String mobile, String captcha) {
        logger.debug(">> phoneBind(" + mobile + "，" + captcha + ")");

        if(StringUtils.isEmpty(openid)){
            throw new OpenmoreException(400, "openId不能为空");
        }
        if(null == type){
            throw new OpenmoreException(400, "登录类型不能为空");
        }
        //没有手机号说明是新用户，新用户校验手机号码
        if(StringUtils.isEmpty(mobile)){
            throw new OpenmoreException(400, "手机号不能为空");
        }
        if(StringUtils.isEmpty(captcha)){
            throw new OpenmoreException(400, "验证码不能为空");
        }
        if(!this.verifyCaptcha(tenantId, mobile, captcha)){
            throw new OpenmoreException("验证码不正确，或已经过期");
        }
        User user = userService.getByOpenid(type, openid);
        //APP首次三方登陆需要在此处创建用户
        if (user == null) {
            throw new OpenmoreException("openId非法");
        }
        //如果三方账户有绑定手机号码则使用三方账户手机号码查询用户，否则使用需要绑定的新号码查看是否存在账户
        String userPhone = StringUtils.isEmpty(user.getPhone())?mobile:user.getPhone();
        UserProfileDto userProfileDto = null;

        //根据手机号码查看已有账户
        User userInfo = userService.getUserByPhone(tenantId,  userPhone);
        //已存在手机号账户
        if (null != userInfo) {
            //校验原账户是否已经绑定三方信息
            if(type == LoginType.WECHAT_OPEN){
                String validateOpenId = userInfo.getWechatOpenid();
                if(!StringUtils.isEmpty(validateOpenId)){
                    throw new OpenmoreException(400, "该手机号账户已绑定微信账号，不能重复绑定");
                }
            }else if(type == LoginType.QQ){
                String validateOpenId = userInfo.getQqOpenid();
                if(!StringUtils.isEmpty(validateOpenId)){
                    throw new OpenmoreException(400, "该手机号账户已绑定QQ账号，不能重复绑定");
                }
            }else if(type == LoginType.WECHAT_MINIAPP){
                String validateOpenId = userInfo.getMiniAppOpenid();
                if(!StringUtils.isEmpty(validateOpenId)){
                    throw new OpenmoreException(400, "该手机号账户已绑定小程序账号，不能重复绑定");
                }
            }
            //更新三方用户信息，若三方账户未绑定手机号码则删除三方账户将账户信息合并入手机号账户
            if(type == LoginType.WECHAT_OPEN){
                userInfo.setWechatOpenid(openid);
            }else if(type == LoginType.QQ){
                userInfo.setQqOpenid(openid);
            }else if(type == LoginType.WECHAT_MINIAPP){
                userInfo.setMiniAppOpenid(openid);
            }
            //删除旧账户
            userService.deleteById(user.getId());
            //更新新账户
            userService.update(userInfo);
            userProfileDto = new UserProfileDto();
            userProfileDto.setId(userInfo.getId());
            userProfileDto.setAvatarUrl(userInfo.getAvatarUrl());
            userProfileDto.setUsername(userInfo.getNickname());
            userProfileDto.setQqOpenId(userInfo.getQqOpenid());
            userProfileDto.setWxOpenId(userInfo.getWechatOpenid());
            userProfileDto.setMiniAppOpenId(userInfo.getMiniAppOpenid());
        }else {
            user.setPhone(userPhone);
            userService.update(user);
            userProfileDto = new UserProfileDto();
            userProfileDto.setId(user.getId());
            userProfileDto.setPhone(CommonUtils.replacePhoneNumber(user.getPhone()));
            userProfileDto.setAvatarUrl(user.getAvatarUrl());
            userProfileDto.setUsername(user.getNickname());
            userProfileDto.setQqOpenId(user.getQqOpenid());
            userProfileDto.setWxOpenId(user.getWechatOpenid());
            userProfileDto.setMiniAppOpenId(user.getMiniAppOpenid());
        }
        OpenmoreToken token = new OpenmoreToken(tenantId, type, openid, userProfileDto.getId(), null);
        shiroLogin(token);
        if(SecurityUtils.getSubject().isAuthenticated()){
            Session session = SecurityUtils.getSubject().getSession();
            userProfileDto.setToken((String) session.getId());
            userProfileDto.setMiniAppOpenId(userProfileDto.getMiniAppOpenId());
            userProfileDto.setWxOpenId(userProfileDto.getWxOpenId());
            userProfileDto.setQqOpenId(userProfileDto.getQqOpenId());
            userProfileDto.setPhone(CommonUtils.replacePhoneNumber(user.getPhone()));
        }
        return userProfileDto;
    }

    @Override
    public boolean verifyCaptcha(String tenantId, String mobile, String captcha) throws OpenmoreException {
        logger.debug(">> verifyCaptcha(" + mobile + "，" + captcha + ")");
        if (StringUtils.isEmpty(mobile)) {
            throw new OpenmoreException("手机号不能为空");
        }
        //TODO ::上线去掉
        if ("8688".equals(captcha)) {
            return true;
        }
        String saveCode = redisOps.get(tenantId+mobile);
        if (saveCode == null) {
            throw new OpenmoreException("验证码已超时，请重新发送");
        }
        if (!saveCode.equals(captcha)) {
            throw new OpenmoreException("验证码不正确");
        }
        return saveCode.equals(captcha);
    }


    /**
     * 使用登录邮箱和手机验证证码安全登录，验证成功后，解锁账号
     *
     * @param email
     * @param captcha
     * @return
     * @throws OpenmoreException
     */
    @Override
    public boolean securityLoginWith(String tenantId, String email, String captcha) throws OpenmoreException {
        logger.debug(">> securityLoginWith(" + email + "，" + captcha + ")");
        if ("8688".equals(captcha)) {
            this.unlockStaff(tenantId, email);
            return true;
        }
        User user = userService.getUserByEmail(tenantId, email);
        if (user == null) {
            throw new OpenmoreException("找不到登录用户的信息");
        }
        String saveCode = redisOps.get(user.getPhone());
        if (saveCode == null) {
            throw new OpenmoreException("验证码已超时，请重新发送");
        }
        if (saveCode.equals(captcha)) {
            this.unlockStaff(tenantId, email);
        }
        return true;
    }

    /**
     * 解锁账号
     *
     * @param email
     */
    private void unlockStaff(String tenantId, String email) {
        User user = userService.getUserByEmail(tenantId, email);
        if (user == null) {
            throw new OpenmoreException("找不到登录用户的信息");
        }
        user.setLocked(false);
        userService.updateNoCache(user);
    }

    @Override
    public UserProfileDto getUserProfile(LoginType type, String openId){
        User user = userService.getByOpenid(type, openId);
        UserProfileDto userProfileDto = null;
        if(null!=user){
            user.setPhone(user.getPhone());
            //更新微信用户信息
            userProfileDto = new UserProfileDto();
            userProfileDto.setId(user.getId());
            userProfileDto.setPhone(user.getPhone());
            userProfileDto.setAvatarUrl(user.getAvatarUrl());
            userProfileDto.setUsername(user.getNickname());
        }
        return userProfileDto;
    }

    public UserProfileDto getLoginInfo() {
//      登录成功后获得登录信息
        User user = getLoginUser();
        if (user == null) {
            logger.error("您当前在跨域下请求接口，无法正常登录");
            throw new OpenmoreException("您当前在跨域下请求接口，无法正常登录");
        }
        UserProfileDto dto = new UserProfileDto();
        Session session = SecurityUtils.getSubject().getSession();
        List<Permission> permList = null;
//      如果是超级管理员，为特定角色，不能通过角色指定，只能通过type身份指定
        if (user.getUserType() == UserType.ROOT) {
//          由于超级管理员没有绑定租户，不能有租户数据操作菜单，只能有【超级管理菜单】
            Role rootRole = roleService.getEntityByCode(Role.ROLE_ROOT);
            List<Role> roles = new ArrayList<>();
            roles.add(rootRole);
            permList = permissionService.getPermissionsByRoles(roles);
        } else if (user.getUserType() == UserType.USER) {
//          居民通过微信登录，没有权限菜单
//            TODO:: 需要添加普通用户的权限
        } else {
//          工作人员，只有配置的权限
            //permList = permissionService.getPermissionsByStaffId(user.getId());
        }
        BeanUtils.copyProperties(user, dto);
        dto.setMenuList(permissionService.findMenuByPermissions(permList));
        dto.setToken((String) session.getId());
        dto.setPhone(CommonUtils.replacePhoneNumber(user.getPhone()));
        dto.setUsername(user.getNickname());
        dto.setAvatarUrl(user.getAvatarUrl());
        dto.setType(user.getUserType());
        dto.setBirthday(user.getBirthday());
        dto.setMiniAppOpenId(user.getMiniAppOpenid());
        dto.setWxOpenId(user.getWechatOpenid());
        dto.setQqOpenId(user.getQqOpenid());
        dto.setWechatName(user.getWechatName());
        if(!StringUtils.isEmpty(user.getPassword())){
            dto.setHasPassword(true);
        }else{
            dto.setHasPassword(false);
        }
        return dto;
    }


}